package com.youlu.campus.admin.zm.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.youlu.campus.admin.zm.dto.SubOrderInfoDTO;
import com.youlu.campus.admin.zm.entity.SimpleOrderInfo;
import com.youlu.campus.admin.zm.entity.SimpleSubOrderInfo;
import com.youlu.campus.admin.zm.msg.MsgObject;
import com.youlu.campus.admin.zm.msg.TransferMsg;
import com.youlu.campus.admin.zm.req.ZmBaseMsgRequest;
import com.youlu.campus.admin.zm.req.ZmReceiveMsgRequest;
import com.youlu.campus.admin.zm.req.ZmReplyMsgRequest;
import com.youlu.campus.admin.zm.utils.ZmUtils;
import com.youlu.campus.base.sys.DelayMsgGray;
import com.youlu.campus.entity.OrderInfo;
import com.youlu.campus.entity.SystemApolloConfig;
import com.youlu.campus.entity.enums.ItemTypeEnum;
import com.youlu.campus.service.course.QueryWrapper;
import com.youlu.campus.service.system.SystemApolloConfigService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author : [dengbo]
 * @className : AsyncZmService
 * @description : [异步发送消息类]
 * @createTime : [2022/11/13 18:05]
 */
@Service
@Slf4j
public class AsyncZmService {

    private static final String ZM_SECRET = "XLy9kSejl6lRkC72zMgI9HPHcLN5aDJnW9XMCDaFW8xltHyLk3iJYjXAzB_XMa8K";

    private static final String ZN_END_URL = "https://xiaokefu.com.cn/thirdService/OpenWePractice/Finish";

    private static final String ASYNC_REPLY_URL = "https://xiaokefu.com.cn/thirdService/OpenWePractice/ReplyMessage";

    private final static String USER_FLOW_INFO = "zm.flow.info:openid:%s:flowId:%s";

    private final static String USER_FLOW_ERROR_TIME = "zm.flow.error.time:openid:%s:flowId:%s";

    /**
     * 缓存流的有效时间 单位s
     */
    private final int USER_FLOW_EXPIRE = 36000;
    /**
     * 输入错误次数有效时间10分钟 单位s
     */
    private final int USER_FLOW_ERROR_EXPIRE = 600;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private SystemApolloConfigService systemApolloConfigService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    public Map<String, Object> sendEndFlow(String openid, Long flowId) {
        ZmBaseMsgRequest request = new ZmBaseMsgRequest();
        request.setFlowId(flowId);
        request.setTime(System.currentTimeMillis() / 1000);
        try {
            String signature = ZmUtils.generateSignature(request, ZM_SECRET);
            request.setSignature(signature);
            ResponseEntity<String> re = restTemplate.postForEntity(ZN_END_URL, request, String.class);
            Map<String, Object> result = JSONObject.parseObject(re.getBody(), Map.class);
            log.info("openid:{} flowid:{} end flow:{}", openid, flowId, result);
            return result;
        } catch (Exception e) {
            log.error("end flow error", e);
        }
        Map<String, Object> result = new HashMap<>();
        result.put("res", 1);
        result.put("msg", "结束流程失败");
        return result;
    }

    public void sendEndFlow(String openid, Long flowId, boolean clearCache) {
        // 结束流程
        sendEndFlow(openid, flowId);
        // 清楚缓存
        if (clearCache) {
            clearFlowCache(openid, flowId);
        }
    }

    public Map<String, Object> sendManualServiceFlow(String openId, Long flowId, boolean endFlow, boolean clearCache) {
        TransferMsg transferMsg = new TransferMsg();
        Map<String, Object> re = sendOneMsg(openId, flowId, transferMsg);
        if (re.get("res").equals("0") || re.get("res").equals(0)) {
            // 自动结束流程
            if (endFlow) {
                sendEndFlow(openId, flowId, clearCache);
            }
        }
        return re;
    }


    public Map<String, Object> sendOneMsg(String openId, Long flowId, MsgObject msg) {
        ZmReplyMsgRequest request = new ZmReplyMsgRequest();
        request.setFlowId(flowId);
        request.setTime(System.currentTimeMillis() / 1000);
        try {
            String signature = ZmUtils.generateSignature(request, ZM_SECRET);
            request.setSignature(signature);
            request.addMsg(msg);
            ResponseEntity<String> re = restTemplate.postForEntity(ASYNC_REPLY_URL, request, String.class);
            Map<String, Object> result = JSONObject.parseObject(re.getBody(), Map.class);
            log.info("openId:{},flowId:{} send one msg success:{}", openId, flowId, result);
            return result;
        } catch (Exception e) {
            log.info("openId:{},flowId:{} send one msg error:{}", openId, flowId, e);
        }
        Map<String, Object> result = new HashMap<>();
        result.put("res", 1);
        result.put("msg", "发送消息失败");
        return result;
    }

    public void sendOneMsg(String openId, Long flowId, MsgObject msg, boolean endFlow, boolean clearCache) {
        sendOneMsg(openId, flowId, msg);
        if (endFlow) {
            sendEndFlow(openId, flowId, clearCache);
        }
    }

    @Async
    public void sendAsyncOneMsg(String openId, Long flowId, MsgObject msg) {
        sendOneMsg(openId, flowId, msg);
        updateFirstMsgEnd(openId, flowId);
    }

    @Async
    public Map<String, Object> sendAsyncOneMsg(String openId, Long flowId, MsgObject msg, boolean endFlow, boolean clearCache) {
        Map<String, Object> result = sendOneMsg(openId, flowId, msg);
        // 结束流程
        if (endFlow) {
            sendEndFlow(openId, flowId, clearCache);
        }
        return result;
    }

    public DelayMsgGray getDelayMsgGray() {
        SystemApolloConfig systemApolloConfig = systemApolloConfigService.find();
        if (systemApolloConfig == null) {
            return null;
        }
        DelayMsgGray delayMsgGray=systemApolloConfig.getDelayMsgGray();
        return delayMsgGray;
    }

    public Map<String, Object> sendMoreMsg(Long flowId, List<MsgObject> msgs) throws Exception {
        ZmReplyMsgRequest request = new ZmReplyMsgRequest();
        request.setFlowId(flowId);
        request.setTime(System.currentTimeMillis() / 1000);
        String signature = ZmUtils.generateSignature(request, ZM_SECRET);
        request.setSignature(signature);
        request.addAll(msgs);
        log.info("request param:{}", request);
        ResponseEntity<String> re = restTemplate.postForEntity(ASYNC_REPLY_URL, request, String.class);
        Map<String, Object> result = JSONObject.parseObject(re.getBody(), Map.class);
        log.info("async replay re:{}", result);
        return result;
    }

    public Pair<Map<String, String>, OrderInfo> getCacheItemType(String openid, Long flowId) {
        Map<String, String> cacheResult = getFlowCache(openid, flowId);
        if (MapUtils.isEmpty(cacheResult)) {
            log.error("openid get cache empty ,flow end，openId:{},flowId:{}", openid, flowId);
            return Pair.of(null, null);
        }
        String orderNo = cacheResult.get("orderNo");
        OrderInfo orderInfo = mongoTemplate.findOne(new Query(Criteria.where("orderNo").is(orderNo)), OrderInfo.class);
        if (orderInfo == null) {
            log.error("openid get cache empty ,flow end，openId:{},flowId:{}", openid, flowId);
            return Pair.of(null, null);
        }
        return Pair.of(cacheResult, orderInfo);
    }

    public SimpleOrderInfo getSimpleOrderInfo(String orderNo) {
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.select("name", "orderNo", "id", "logisticsNo", "payTime", "status", "refundStatus", "mobile");
        queryWrapper.where("orderNo", orderNo);
        return mongoTemplate.findOne(queryWrapper, SimpleOrderInfo.class);
    }

    public List<SimpleSubOrderInfo> getSimpleSubOrderInfo(String orderNo) {
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.select("orderNo", "caItemType", "id", "caGift", "logisticsCompany", "logisticsNo", "updatedTime", "postSaleStatus", "postSaleLogisticsNo", "postSaleLogisticsCompany");
        queryWrapper.where("orderNo", orderNo);
        return mongoTemplate.find(queryWrapper, SimpleSubOrderInfo.class);
    }

    public SubOrderInfoDTO getItemSubInfo(String orderNo) {
        List<SimpleSubOrderInfo> subOrderInfos = getSimpleSubOrderInfo(orderNo);
        SubOrderInfoDTO dto = new SubOrderInfoDTO();
        boolean hasCaGift = false;
        boolean hasCa = false;
        for (SimpleSubOrderInfo orderInfo : subOrderInfos) {
            // 补发
            if("1".equals(orderInfo.getPostSaleStatus())){
                dto.getLogisticsNoList().add(orderInfo.getPostSaleLogisticsNo());
                dto.getLogisticsCompanyList().add(orderInfo.getPostSaleLogisticsCompany());
            }else {
                dto.getLogisticsNoList().add(orderInfo.getLogisticsNo());
                dto.getLogisticsCompanyList().add(orderInfo.getLogisticsCompany());
            }
            if (hasCa && hasCaGift) {
                continue;
            }
            // 新的逻辑
            if (orderInfo.getCaItemType() != null) {
                if (orderInfo.getCaItemType() == ItemTypeEnum.CA_AND_GIFT.getValue()) {
                    hasCa = true;
                    hasCaGift = true;
                    continue;
                }
                if (orderInfo.getCaItemType() == ItemTypeEnum.CA.getValue()) {
                    hasCa = true;
                    continue;
                }
                hasCaGift = true;
                continue;
            }
            // 老的逻辑
            if (orderInfo.getCaGift() == null || !orderInfo.getCaGift()) {
                hasCa = true;
            } else {
                hasCaGift = true;
            }
        }
        // 设置物流单号
        dto.setLogisticsNos(dto.getLogisticsNoList().stream().filter(item->StringUtils.isNotBlank(item))
                .collect(Collectors.joining(",")));
        // 设置物流公司
        dto.setLogisticsCompanys(dto.getLogisticsCompanyList().stream().filter(item->StringUtils.isNotBlank(item))
                .collect(Collectors.joining(",")));
        if (hasCa && hasCaGift) {
            dto.setItemType(ItemTypeEnum.CA_AND_GIFT.getValue());
        } else if (hasCa) {
            dto.setItemType(ItemTypeEnum.CA.getValue());
        } else {
            dto.setItemType(ItemTypeEnum.GIFT.getValue());
        }
        return dto;
    }

    public void setFlowCache(String key, Map<String, String> cacheResult) {
        // Hash批量设置字段
        redisTemplate.opsForHash().putAll(key, cacheResult);
        // 设置过期时间
        redisTemplate.expire(key, USER_FLOW_EXPIRE, java.util.concurrent.TimeUnit.SECONDS);
    }

    public Map<String, String> setFlowCache(ZmReceiveMsgRequest req, String orderNo, String phone, boolean isDelayMsg) {
        SubOrderInfoDTO itemInfo = getItemSubInfo(orderNo);
        Map<String, String> cacheResult = new HashMap<>();
        cacheResult.put("openid", req.getOpenid());
        cacheResult.put("appid", req.getAppid());
        cacheResult.put("flowId", String.valueOf(req.getFlowId()));
        cacheResult.put("orderNo", orderNo);
        cacheResult.put("phone", phone);
        cacheResult.put("itemType", String.valueOf(itemInfo.getItemType()));
        cacheResult.put("logisticsNos", itemInfo.getLogisticsNos());
        cacheResult.put("logisticsCompanys", itemInfo.getLogisticsCompanys());
        cacheResult.put("time", "1");
        if (isDelayMsg) {
            cacheResult.put("firstMsgEnd", "0");
        }
        String key = getFlowCacheKey(req.getOpenid(), req.getFlowId());
        setFlowCache(key, cacheResult);
        return cacheResult;
    }

    public Map<String, String> getFlowCache(String key) {
        // 获取Hash所有字段和值，转换为Map<String, String>
        return redisTemplate.opsForHash().entries(key)
                .entrySet().stream()
                .collect(Collectors.toMap(
                        entry -> entry.getKey().toString(),
                        entry -> entry.getValue() != null ? entry.getValue().toString() : ""
                ));
    }

    public Map<String, String> getFlowCache(String openid, Long flowId) {
        String key = getFlowCacheKey(openid, flowId);
        return getFlowCache(key);
    }

    public void clearFlowCache(String openid, Long flowId) {
        String key = getFlowCacheKey(openid, flowId);
        // 删除指定Key
        redisTemplate.delete(key);
    }

    public void updateFirstMsgEnd(String openid, Long flowId) {
        String key = getFlowCacheKey(openid, flowId);
        // Hash单个字段更新
        redisTemplate.opsForHash().put(key, "firstMsgEnd", "1");
    }

    public long incErrorTime(String openid, Long flowId) {
        String key = getFlowErrorTimeKey(openid, flowId);
        // 自增操作
        Long num = redisTemplate.opsForValue().increment(key, 1);
        // 设置过期时间
        redisTemplate.expire(key, USER_FLOW_ERROR_EXPIRE, java.util.concurrent.TimeUnit.SECONDS);
        return num != null ? num : 0;
    }

    public void delErrorTime(String openid, Long flowId) {
        String key = getFlowErrorTimeKey(openid, flowId);
        redisTemplate.delete(key);
    }

    public boolean inputErrorTimeMore3(String openid, Long flowId, Integer errorNum) {
        long num = incErrorTime(openid, flowId);
        log.info("openi={},flowId={} input error time:{}", openid, flowId, num);
        errorNum = errorNum == null ? 3 : errorNum;
        if (num >= errorNum) {
            return true;
        }
        return false;
    }

    public void clearErrorTimeMore3(String openid, Long flowId) {
        delErrorTime(openid, flowId);
    }

    public static String getFlowErrorTimeKey(String openid, Long flowId) {
        return String.format(USER_FLOW_ERROR_TIME, openid, flowId);
    }

    public static String getFlowCacheKey(String openid, Long flowId) {
        return String.format(USER_FLOW_INFO, openid, flowId);
    }
}